#pragma once

#include <map>
#include <vector>
#include <deque>
#include <sstream>

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/deque.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/set.hpp>

#include <arpa/inet.h>

#include "Serialization.h"
#include "Utils.h"

using namespace boost::archive;

// ================================================================================

typedef uint32_t IPAddress;

static char* toString(IPAddress addr) {
  in_addr ia;

  ia.s_addr = addr;
  return inet_ntoa(ia);
}

// ================================================================================

// The network description (Agent <-> Collector)

struct Network {

  // Minimal addressable network unit

  struct Interface {
    Interface() { }

    Interface(IPAddress addr) {
      address = addr;
      inBytes = outBytes = 0;
    }

    // Serialization

    template<typename A>
    void serialize(A& ar, const unsigned int v) {
      ar & name;
      ar & address;
      ar & netmask;
      ar & inBytes;
      ar & outBytes;
    }

    // Payload

    std::string name;

    IPAddress address;
    IPAddress netmask;

    size_t inBytes;
    size_t outBytes;
  };

  typedef std::map<IPAddress, Interface> Interfaces;

  // --------------------------------------------------------------------------------
  
  // A group of interfaces

  struct Subnet {
    typedef std::set<IPAddress> Nodes;

    Subnet() { }

    Subnet(IPAddress id_, IPAddress mask_) {
      id = id_;
      mask = mask_;
    }

    // Serialization

    template<typename A>
    void serialize(A& ar, const unsigned int v) {
      ar & id;
      ar & mask;
      ar & nodes;
    }


    // Payload

    IPAddress id;
    IPAddress mask;
    Nodes nodes;
  };

  typedef std::map<IPAddress, Subnet> Subnets;

  // --------------------------------------------------------------------------------
  
  // A network device

  struct Node {
    typedef std::deque<IPAddress> Interfaces;
    typedef std::map<unsigned int, IPAddress> IfIndexes;

    Node() { }

    // Access

    void addInterface(unsigned int ifIdx, IPAddress ipAddr) {
      ifs.push_back(ipAddr);
      ifIndexes[ifIdx] = ipAddr;
    }

    // Serialization

    template<typename A>
    void serialize(A& ar, const unsigned int v) {
      ar & descr;
      ar & ifs;
      ar & ifIndexes;
    }

    // Payload

    std::string descr;
    Interfaces ifs;
    IfIndexes ifIndexes;
  };

  typedef std::deque<Node> Nodes;

  // --------------------------------------------------------------------------------

  // Serialization
  
  template<typename A>
  void serialize(A& ar, const unsigned int v) {
    ar & id;
    ar & interfaces;
    ar & subnets;
    ar & nodes;
  }

  XmlNode* serialize(net::ExtXml& xml) {
    XmlNode* root = allocNode(xml, "network");

    addAttr(xml, root, "id", id.c_str());
    
    for (Subnets::iterator sit = subnets.begin(); sit != subnets.end(); ++sit) {
      Subnet& subnet = sit->second;
      XmlNode* subnetNode = addNode(xml, root, "subnet");
      addAttr(xml, subnetNode, "id", toString(subnet.id));
      addAttr(xml, subnetNode, "mask", toString(subnet.mask));

      for (Subnet::Nodes::iterator nit = subnet.nodes.begin(); nit != subnet.nodes.end(); ++nit) {
        XmlNode* netNode = addNode(xml, subnetNode, "node");
        Interface* iface = interface(*nit);
        addAttr(xml, netNode, "ip", toString(iface->address));
        //addAttr(xml, netNode, "mask", toString(iface->netmask));
      }
    }

    for (Nodes::iterator rit = nodes.begin(); rit != nodes.end(); ++rit) {
      XmlNode* routerNode = addNode(xml, root, "router");
      Node& router = *rit;

      addAttr(xml, routerNode, "descr", router.descr.c_str());

      for (Node::Interfaces::iterator ifit = router.ifs.begin(); ifit != router.ifs.end(); ++ifit) {
        XmlNode* ifNode = addNode(xml, routerNode, "interface");
        Interface* iface = interface(*ifit);

        addAttr(xml, ifNode, "ip", toString(iface->address));
        addAttr(xml, ifNode, "mask", toString(iface->netmask));
        addAttr(xml, ifNode, "subnet-id", toString(iface->address & iface->netmask));
        addAttr(xml, ifNode, "name", iface->name.c_str());
        addAttr(xml, ifNode, "inBytes", toString(iface->inBytes).c_str());
        addAttr(xml, ifNode, "outBytes", toString(iface->outBytes).c_str());
      }
    }

    return root;
  }

  // Access
  
  Interface* interface(IPAddress ipAddr) {
    Interfaces::iterator it = interfaces.find(ipAddr);

    if (it != interfaces.end()) {
      return &it->second; 
    } else {
      return NULL;
    }
  }

  void addInterface(IPAddress ipAddr) {
    interfaces.insert(std::make_pair(ipAddr, Network::Interface(ipAddr)));

    // Try to match the IP address with any existing subnets
    for (Subnets::iterator it = subnets.begin(); it != subnets.end(); ++it) {
      if ((ipAddr & it->second.mask) == it->second.id) {
        it->second.nodes.insert(ipAddr);
      }
    }
  }

  void addSubnet(Interface* iface) {
    IPAddress subnetId = iface->address & iface->netmask;
    
    if (subnets.find(subnetId) == subnets.end()) {
      subnets.insert(std::make_pair(subnetId, Subnet(subnetId, iface->netmask)));
    }

    subnet(iface)->nodes.insert(iface->address);
  }

  Subnet* subnet(Interface* iface) {
    IPAddress subnetId = iface->address & iface->netmask;

    return &subnets.find(subnetId)->second;
  }

  void clear() {
    interfaces.clear();
    nodes.clear();
    subnets.clear();
  }

  // Payload

  Interfaces interfaces;
  Subnets subnets;
  Nodes nodes;

  std::string id;
};
